﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Globalization;

using NLite.Data;

namespace NLite.Data.Dialect.Function
{
    public interface IFunctionView
    {
        void Render(NodeWriter visitor, params INode[] args);
    }

    public static class FunctionType
    {
        public const string
            ToChar = "to_char"
            , ToNChar = "to_nchar"
            , Case = "Case"
            , Cast ="Cast"
            ;

        public static class String
        {
            public const string Lower = "Lower",
            Upper = "Upper",
            Trim = "Trim",
            LTrim = "LTrim",
            RTrim = "RTrim",
            SubString = "substr",
            Concat = "Concat",
            Replace = "Replace",
            IndexOf = "instr",
            Length ="Length",
            Left = "Left",
            Right ="Right",
            Remove="Remove"
            ;
        }

        public static class Math
        {
            public const string Abs = "Abs",
            Acos = "Acos",
            Asin = "Asin",
            Atan = "Atan",
            Atan2 = "Atan2",
            Ceiling = "Ceiling",
            Cos = "Cos",
            Cosh = "Cosh",
            DivRem = "DivRem",
            Exp = "Exp",
            Floor = "Floor",
            //IEEERemainder = "IEEERemainder",
            Log = "Log",
            Log10 = "Log10",
            Power = "Power",
            Mod = "Mod",
            Round = "Round",
            Sign = "Sign",
            Sin = "Sin",
            Sinh = "Sinh",
            Sqrt = "Sqrt",
            Tan = "Tan",
            Tanh = "Tanh",
            Truncate = "Trunc",
            Random="Random"
            ;
        }

        public static class DateTime
        {
            public const string
                Current = "current_date"
                ,GetDay = "get_day"
                , GetMonth = "get_month"
                , GetYear = "get_year"
                , GetHour = "get_hour"
                , GetMinute = "get_minute"
                , GetSecond = "get_second"

                , GetMillisecond = "get_millisecond"
                , GetDayOfWeek = "get_DayOfWeek"
                , GetDayOfYear = "get_DayOfYear"

                , New = "new_Date"
                , AddDay = "add_day"
                , AddMonth = "add_month"
                , AddYear = "add_year"
                , AddHour = "add_hour"
                , AddMinute = "add_minute"
                , AddSecond = "add_second"

                ,Add = "add_date"
                ,Subtract = "diff_date"
                ;
        }
    }

    public static class FunctionView
    {
        public static IFunctionView ToChar
        {
            get { return Get(FunctionType.ToChar); }
        }

        public static IFunctionView ToNChar
        {
            get { return Get(FunctionType.ToNChar); }
        }

        public static IFunctionView Case
        {
            get { return Get(FunctionType.Case); }
        }

        public static IFunctionView Cast
        {
            get { return Get(FunctionType.Cast); }
        }

        public static class String
        {
           
           
            public static IFunctionView Lower
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Lower); }
            }

            public static IFunctionView Upper
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Upper); }
            }
            public static IFunctionView Trim
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Trim); }
            }
            public static IFunctionView LTrim
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.LTrim); }
            }
            public static IFunctionView RTrim
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.RTrim); }
            }

            public static IFunctionView Concat
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Concat); }
            }
            public static IFunctionView Replace
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Replace); }
            }
            public static IFunctionView SubString
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.SubString); }
            }
            public static IFunctionView IndexOf
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.IndexOf); }
            }
            public static IFunctionView Length
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Length); }
            }
            public static IFunctionView Left
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Left); }
            }
            public static IFunctionView Right
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Right); }
            }
            public static IFunctionView Remove
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.String.Remove); }
            }
        }

        public static class Math
        {
            public static IFunctionView Abs
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Abs); }
            }
            public static IFunctionView Acos
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Acos); }
            }

            public static IFunctionView Asin
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Asin); }
            }
            public static IFunctionView Atan
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Atan); }
            }

            public static IFunctionView Atan2
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Atan2); }
            }
            public static IFunctionView Ceiling
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Ceiling); }
            }

            public static IFunctionView Cos
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Cos); }
            }
            public static IFunctionView Cosh
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Cosh); }
            }
            public static IFunctionView DivRem
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.DivRem); }
            }

            public static IFunctionView Exp
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Exp); }
            }
            public static IFunctionView Floor
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Floor); }
            }

            //public static IFunctionView IEEERemainder
            //{
            //    get { return DbConfig.Dialect.GetFunction(FunctionType.Math.IEEERemainder); }
            //}
            public static IFunctionView Log
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Log); }
            }
            public static IFunctionView Log10
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Log10); }
            }
            public static IFunctionView Power
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Power); }
            }
            public static IFunctionView Mod
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Mod); }
            }
            public static IFunctionView Round
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Round); }
            }

            public static IFunctionView Sign
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Sign); }
            }
            public static IFunctionView Sin
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Sin); }
            }
            public static IFunctionView Sinh
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Sinh); }
            }
            public static IFunctionView Sqrt
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Sqrt); }
            }
            public static IFunctionView Tan
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Tan); }
            }

            public static IFunctionView Tanh
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Tanh); }
            }
            public static IFunctionView Truncate
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Truncate); }
            }
            public static IFunctionView Random
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.Math.Random); }
            }
        }

        public static class DateTime
        {
            public static IFunctionView Current
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.Current); }
            }

            public static IFunctionView Add
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.Add); }
            }
            public static IFunctionView Subtract
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.Subtract); }
            }
            
            public static IFunctionView AddDay
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddDay); }
            }
            public static IFunctionView AddHour
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddHour); }
            }

            public static IFunctionView AddMinute
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddMinute); }
            }
            public static IFunctionView AddMonth
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddMonth); }
            }

            public static IFunctionView AddSecond
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddSecond); }
            }
            public static IFunctionView AddYear
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.AddYear); }
            }

            public static IFunctionView GetDay
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetDay); }
            }
            public static IFunctionView GetDayOfWeek
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetDayOfWeek); }
            }
            public static IFunctionView GetDayOfYear
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetDayOfYear); }
            }

            public static IFunctionView GetHour
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetHour); }
            }
            public static IFunctionView GetMillisecond
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetMillisecond); }
            }

            public static IFunctionView GetMinute
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetMinute); }
            }
            public static IFunctionView GetMonth
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetMonth); }
            }
            public static IFunctionView GetSecond
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetSecond); }
            }
            public static IFunctionView GetYear
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.GetYear); }
            }
            public static IFunctionView New
            {
                get { return DbConfig.Dialect.GetFunction(FunctionType.DateTime.New); }
            }
        }

        public static IFunctionView Get(string name)
        {
            return DbConfig.Dialect.GetFunction(name);
        }
    }


    public class ProxyFunctionView : IFunctionView
    {
        Action<NodeWriter, INode[]> proxy;

        public ProxyFunctionView(Action<NodeWriter, INode[]> proxy)
        {
            if (proxy == null)
                throw new ArgumentNullException("proxy");
            this.proxy = proxy;
        }

        public void Render(NodeWriter visitor, params INode[] args)
        {
            proxy(visitor,args);
        }
    }
    [Serializable]
    public class StandardFunctionView : IFunctionView
    {
        protected readonly string name;

        /// <summary>
        /// Initializes a new instance of the StandardSQLFunction class.
        /// </summary>
        /// <param name="name">SQL function name.</param>
        public StandardFunctionView(string name)
        {
            this.name = name.ToUpper();
        }

        #region ISQLFunction Members

        public virtual void Render(NodeWriter visitor, params INode[] args)
        {
            visitor.Builder.Append(name).Append("(");
            visitor.VisitEnumerable(args);
            visitor.Builder.Append(")");
        }

        #endregion

        public override string ToString()
        {
            return name;
        }
    }

    [Serializable]
    public class StandardSafeFunctionView : StandardFunctionView
    {
        private int allowedArgsCount = 1;
        /// <summary>
        /// Initializes a new instance of the StandardSafeSQLFunction class.
        /// </summary>
        /// <param name="name">SQL function name.</param>
        /// <param name="allowedArgsCount">Exact number of arguments expected.</param>
        public StandardSafeFunctionView(string name, int allowedArgsCount)
            : base(name)
        {
            this.allowedArgsCount = allowedArgsCount;
        }

        public override void Render(NodeWriter visitor, params INode[] args)
        {
            if (args.Length != allowedArgsCount)
            {
                throw new ArgumentException(string.Format("function '{0}' takes {1} arguments.", name, allowedArgsCount));
            }
            base.Render(visitor, args);
        }
    }

    [Serializable]
    public class FunctionTemplateView : IFunctionView
    {
        private const int InvalidArgumentIndex = -1;
        private static readonly Regex SplitRegex = new Regex("(\\?[0-9]+)");

        private struct TemplateChunk
        {
            public string Text; // including prefix if parameter
            public int ArgumentIndex;

            public TemplateChunk(string chunk, int argIndex)
            {
                Text = chunk;
                ArgumentIndex = argIndex;
            }
        }

        private readonly bool hasArguments;

        private readonly string template;
        private TemplateChunk[] chunks;


        public FunctionTemplateView(string template)
        {
            this.template = template;

            InitFromTemplate();
            hasArguments = chunks.Length > 1;
        }

        private void InitFromTemplate()
        {
            string[] stringChunks = SplitRegex.Split(template);
            chunks = new TemplateChunk[stringChunks.Length];

            for (int i = 0; i < stringChunks.Length; i++)
            {
                string chunk = stringChunks[i];
                if (i % 2 == 0)
                {
                    // Text part.
                    chunks[i] = new TemplateChunk(chunk, InvalidArgumentIndex);
                }
                else
                {
                    // Separator, i.e. argument
                    int argIndex = int.Parse(chunk.Substring(1), CultureInfo.InvariantCulture);
                    chunks[i] = new TemplateChunk(stringChunks[i], argIndex);
                }
            }
        }

        public void Render(NodeWriter visitor, params INode[] args)
        {
            foreach (TemplateChunk tc in chunks)
            {
                if (tc.ArgumentIndex != InvalidArgumentIndex)
                {
                    int adjustedIndex = tc.ArgumentIndex - 1; // Arg indices are one-based
                    var arg = adjustedIndex < args.Length ? args[adjustedIndex] : null;
                    if (arg != null)
                        visitor.Visit(arg);
                }
                else
                    visitor.Builder.Append(tc.Text);
            }
           
        }

        public override string ToString()
        {
            return template;
        }
    }
         
    [Serializable]
    public class NoArgFunctionView : IFunctionView
    {
     
        public NoArgFunctionView(string name)
        {
            Name = name.ToUpper();
        }
        public bool HasArguments
        {
            get { return false; }
        }

        public string Name { get; protected set; }

        public void Render(NodeWriter visitor, params INode[] args)
        {
            visitor.Builder.Append(Name).Append("()");
        }
    }

    [Serializable]
    public class VarArgsFunctionView : IFunctionView
    {
        private readonly string begin;
        private readonly string sep;
        private readonly string end;

        public VarArgsFunctionView(string begin, string sep, string end)
        {
            this.begin = begin;
            this.sep = sep;
            this.end = end;
        }

        public void Render(NodeWriter visitor, params INode[] args)
        {
           visitor.Builder.Append(begin);
           visitor.VisitEnumerable(args, sep);
           visitor.Builder.Append(end);
        }

    }

    public class CaseFunctionView : IFunctionView
    {
        public void Render(NodeWriter visitor, params INode[] args)
        {
            if (args.Length != 2 && args.Length != 3)
            {
                throw new ArgumentException("function '{0}' takes 2 or 3 arguments.");
            }

            var whens = (args[0] as ICollection<INode>).ToArray();
            var thens = (args[1] as ICollection<INode>).ToArray();
            var @else = args[2] as INode;

            visitor.Builder.Append("CASE");

            var lenght = whens.Length;
            for (int i = 0; i < lenght; i++)
            {
                visitor.Builder.Append(" WHEN ");
                visitor.Visit(whens[i]);
                visitor.Builder.Append(" THEN ");
                visitor.Visit(thens[i]);
            }

            if (@else != null)
            {
                visitor.Builder.Append(" ELSE ");
                visitor.Visit(@else);
            }

            visitor.Builder.Append(" END");
        }
    }

    [Serializable]
    public class CastFunctionView : IFunctionView
    {

        public void Render(NodeWriter visitor, params INode[] args)
        {
            if (args == null || args.Length != 2)
            {
                throw new ArgumentException("cast() requires two arguments");
            }
            var type = args[1] as SqlObject;
            if(type == null)
                throw new ArgumentException("cast() requires PrimitiveType argument");

            var sqlType = DbConfig.Dialect.GetTypeName( type.Type);

            visitor.Builder.Append("CAST(");
            visitor.Visit(args[0]);
            visitor.Builder.Append(" AS ");
            visitor.Builder.Append(sqlType.ToString());
            visitor.Builder.Append(")");
        }
    }
}
